use petgraph::{algo::dijkstra, csr::DefaultIx, graph::{NodeIndex, UnGraph}, visit::EdgeRef, Direction};
use std::{collections::HashMap, ops::Add, path};

use crate::model::cptem::*;

// #[derive(Debug, Clone)]
// pub struct NetworkState {
//     pub states: HashMap<String, State>, // states: HashMap<String, &ComponentProbabilityTimedEvolutionModel>
// }

// impl NetworkState {
//     pub fn new() -> NetworkState {
//         NetworkState {
//             states: HashMap::new(),
//         }
//     }

//     pub fn add_state(&mut self, state: State) {
//         self.states.insert(state.name.clone(), state);
//     }

//     pub fn get_state(&self, name: &str) -> Option<&State> {
//         self.states.get(name)
//     }

//     pub fn get_state_mut(&mut self, name: &str) -> Option<&mut State> {
//         self.states.get_mut(name)
//     }

//     pub fn get_states(&self) -> Vec<&State> {
//         self.states.values().collect()
//     }

//     pub fn to_string(&self) -> String {
//         format!("NetworkState({:?})", self.states)
//     }
// }

// #[cfg(test)]
// mod test_network_state {
//     use crate::model::nptem::*;

//     #[test]
//     fn test_new() {
//         let network_state = NetworkState::new();
//         println!("{:?}", network_state)
//     }

//     #[test]
//     fn test_add_state() {
//         let mut network_state = NetworkState::new();
//         let state = State::new("test".to_string(), true);
//         network_state.add_state(state);
//         println!("{:?}", network_state)
//     }

//     #[test]
//     fn test_get_state() {
//         let mut network_state = NetworkState::new();
//         let state = State::new("test".to_string(), true);
//         network_state.add_state(state);
//         let state = network_state.get_state("test");
//         println!("{:?}", state)
//     }

//     #[test]
//     fn test_get_states() {
//         let mut network_state = NetworkState::new();
//         let state1 = State::new("test1".to_string(), true);
//         let state2 = State::new("test2".to_string(), false);
//         network_state.add_state(state1);
//         network_state.add_state(state2);
//         let states = network_state.get_states();
//         println!("{:?}", states)
//     }

//     #[test]
//     fn test_to_string() {
//         let mut network_state = NetworkState::new();
//         let state1 = State::new("test1".to_string(), true);
//         let state2 = State::new("test2".to_string(), false);
//         network_state.add_state(state1);
//         network_state.add_state(state2);
//         let states = network_state.to_string();
//         println!("{:?}", states)
//     }
// }

// #[derive(Debug, Clone)]
// pub enum NodeData {
//     PhysicalNodedata,
//     ServeiceNodeData,
//     Application,
// }

#[derive(Debug, Clone)]
pub struct PhysicalNodedata {
    pub name: String,
    pub load: f64,
    pub capacity: f64,
    pub delay: f64,
    // pub cpu: f64,
}

#[derive(Debug, Clone)]
pub struct PhysicalEdgeData {
    pub name: String,
    pub delay: f64,
    pub load: f64,
    pub capacity: f64,
}

#[derive(Debug, Clone)]
pub struct ServeiceNodeData {
    pub name: String,
    pub delay: f64,
    pub load: f64,
    pub capacity: f64,
    pub backup_strategy: BackupStrategy,
}

#[derive(Debug, Clone)]
pub struct SeriviceEdgeData {
    pub name: String,
    // pub delay: f64,
    // pub load: f64,
    // pub capacity: f64,
    pub route_policy: RoutePolicy,
    pub route_path: Vec<String>,
}

#[derive(Debug, Clone)]
pub struct Application {
    pub name: String,
    pub service_fuction_chain: Vec<String>,
    pub load_threshold: f64,
    pub delay_threshold: f64,
}

#[derive(Debug, Clone)]
pub enum BackupStrategy {
    Normal(String),
    HotStandby(String, String),
    ColdStandby(String, String),
    LoadBalance(Vec<String>),
}

#[derive(Debug, Clone)]
pub enum RoutePolicy {
    None,
    LoadBalance,
    Failover,
}

#[derive(Debug, Clone)]
pub struct NetworkTopology {
    pub physical_nodes: HashMap<String, PhysicalNodedata>,
    pub physical_edges: HashMap<(String, String), PhysicalEdgeData>,
    pub service_nodes: HashMap<String, ServeiceNodeData>,
    pub service_edges: HashMap<(String, String), SeriviceEdgeData>,
    pub applications: HashMap<String, Application>,
    pub topo: UnGraph<PhysicalNodedata, PhysicalEdgeData>,
    pub nidx_map: HashMap<String, NodeIndex<DefaultIx>>,
}

impl NetworkTopology {
    pub fn new(
        physical_nodes: HashMap<String, PhysicalNodedata>,
        physical_edges: HashMap<(String, String), PhysicalEdgeData>,
        service_nodes: HashMap<String, ServeiceNodeData>,
        mut service_edges: HashMap<(String, String), SeriviceEdgeData>,
        applications: HashMap<String, Application>,
    ) -> NetworkTopology {
        let mut topo = UnGraph::<PhysicalNodedata, PhysicalEdgeData>::new_undirected();
        let mut nidx_map = HashMap::new();

        // add edges in physical_edges to topo
         for ((src, dst), edge) in physical_edges.iter() {
            nidx_map.entry(src.clone()).or_insert_with_key(|k| topo.add_node(physical_nodes[k].clone()));
            nidx_map.entry(dst.clone()).or_insert_with_key(|k| topo.add_node(physical_nodes[k].clone()));
            topo.add_edge(
                nidx_map[src],
                nidx_map[dst],
                edge.clone()
            );
        }

        // update route path in service_edges
        for ((src, dst), edge) in service_edges.iter_mut() {
            let src_idx = nidx_map[src];
            let dst_idx = nidx_map[dst];

            let res = dijkstra(&topo, src_idx, Some(dst_idx), 
                |e| e.weight().delay);
            
            let mut path = vec![dst_idx];
            let mut cur_node = dst_idx;

            // while cur_node != src_idx {
            //     let m = topo
            //         .edges_directed(cur_node, Direction::Incoming)
            //         .map(|edge| res.get(&edge.source()).unwrap())
            //         .min()
            //         .unwrap();
            //     let v = *m as usize;
            //     path.push(v);
            //     cur_node = NodeIndex::new(v);
            // }

            // let mut path_node = vec![];
            // for i in path.iter().rev().map(|v| topo[NodeIndex::new(*v)]) {
            //     path_node.push(i);
            // }
        }


        let network_topology = NetworkTopology {
            physical_nodes,
            physical_edges,
            service_nodes,
            service_edges,
            applications,
            topo,
            nidx_map,
        };

        network_topology
    }

}

#[cfg(test)]
mod test_network_topology {
    use super::*;

    #[test]
    fn test_new() {
        let physical_nodes = HashMap::from(
            [
                ("n1".to_string(), PhysicalNodedata {
                    name: "n1".to_string(),
                    load: 0.0,
                    capacity: 100.0,
                    delay: 0.0,
                }),
                ("n2".to_string(), PhysicalNodedata {
                    name: "n2".to_string(),
                    load: 0.0,
                    capacity: 100.0,
                    delay: 0.0,
                }),
                ("n3".to_string(), PhysicalNodedata {
                    name: "n3".to_string(),
                    load: 0.0,
                    capacity: 100.0,
                    delay: 0.0,
                }),
                ("n4".to_string(), PhysicalNodedata {
                    name: "n4".to_string(),
                    load: 0.0,
                    capacity: 100.0,
                    delay: 0.0,
                }),
            ]
        );

        let physical_edges = HashMap::from(
            [
                (("n1".to_string(), "n2".to_string()), PhysicalEdgeData {
                    name: "e1".to_string(),
                    load: 0.0,
                    capacity: 100.0,
                    delay: 0.0,
                }),
                (("n1".to_string(), "n3".to_string()), PhysicalEdgeData {
                    name: "e2".to_string(),
                    load: 0.0,
                    capacity: 100.0,
                    delay: 0.0,
                }),
                (("n2".to_string(), "n3".to_string()), PhysicalEdgeData {
                    name: "e3".to_string(),
                    load: 0.0,
                    capacity: 100.0,
                    delay: 0.0,
                }),
                (("n2".to_string(), "n4".to_string()), PhysicalEdgeData {
                    name: "e4".to_string(),
                    load: 0.0,
                    capacity: 100.0,
                    delay: 0.0,
                }),
                (("n3".to_string(), "n4".to_string()), PhysicalEdgeData {
                    name: "e5".to_string(),
                    load: 0.0,
                    capacity: 100.0,
                    delay: 0.0,
                }),
            ]
        );
        
        let service_nodes = HashMap::from(
            [
                ("s1".to_string(), ServeiceNodeData {
                    name: "s1".to_string(),
                    load: 0.0,
                    capacity: 100.0,
                    delay: 0.0,
                    backup_strategy: BackupStrategy::Normal("n1".to_string()),
                }),
                ("s2".to_string(), ServeiceNodeData {
                    name: "s2".to_string(),
                    load: 0.0,
                    capacity: 100.0,
                    delay: 0.0,
                    backup_strategy: BackupStrategy::HotStandby("n2".to_string(), "n3".to_string()),
                }),
                ("s3".to_string(), ServeiceNodeData {
                    name: "s3".to_string(),
                    load: 0.0,
                    capacity: 100.0,
                    delay: 0.0,
                    backup_strategy: BackupStrategy::Normal("n4".to_string()),
                }),
            ]
        );
        
        let service_edges = HashMap::from(
            [
                (("s1".to_string(), "s2".to_string()), SeriviceEdgeData {
                    name: "s1-s2".to_string(),
                    route_policy: RoutePolicy::Failover,
                    route_path: vec![]
                }),
                (("s2".to_string(), "s3".to_string()), SeriviceEdgeData {
                    name: "s1-s2".to_string(),
                    route_policy: RoutePolicy::Failover,
                    route_path: vec![]
                }),
            ]
        );

        let application = HashMap::from(
            [
                ("a1".to_string(), Application {
                    name: "a1".to_string(),
                    service_fuction_chain: vec!["s1".to_string(), "s2".to_string(), "s3".to_string()],
                    load_threshold: 20.0,
                    delay_threshold: 10.0,
                }),
            ]
        );
        
        let network_state = NetworkTopology::new(physical_nodes, physical_edges, service_nodes, service_edges, application);
        
        println!("{:#?}", network_state);

    }
}
